In [1]:
!pip install kaggle
Requirement already satisfied: kaggle in /usr/local/lib/python3.10/dist-packages (1.6.14)
Requirement already satisfied: six>=1.10 in /usr/local/lib/python3.10/dist-packages (from kaggle) (1.16.0)
Requirement already satisfied: certifi>=2023.7.22 in /usr/local/lib/python3.10/dist-packages (from kaggle) (2024.6.2)
Requirement already satisfied: python-dateutil in /usr/local/lib/python3.10/dist-packages (from kaggle) (2.8.2)
Requirement already satisfied: requests in /usr/local/lib/python3.10/dist-packages (from kaggle) (2.31.0)
Requirement already satisfied: tqdm in /usr/local/lib/python3.10/dist-packages (from kaggle) (4.66.4)
Requirement already satisfied: python-slugify in /usr/local/lib/python3.10/dist-packages (from kaggle) (8.0.4)
Requirement already satisfied: urllib3 in /usr/local/lib/python3.10/dist-packages (from kaggle) (2.0.7)
Requirement already satisfied: bleach in /usr/local/lib/python3.10/dist-packages (from kaggle) (6.1.0)
Requirement already satisfied: webencodings in /usr/local/lib/python3.10/dist-packages (from bleach->kaggle) (0.5.1)
Requirement already satisfied: text-unidecode>=1.3 in /usr/local/lib/python3.10/dist-packages (from python-slugify->kaggle) (1.3)
Requirement already satisfied: charset-normalizer<4,>=2 in /usr/local/lib/python3.10/dist-packages (from requests->kaggle) (3.3.2)
Requirement already satisfied: idna<4,>=2.5 in /usr/local/lib/python3.10/dist-packages (from requests->kaggle) (3.7)

Literature¶

The paper "Deep Learning-based Fruit Freshness Classification and Detection with CMOS Image Sensors and Edge Processors" explores advancements and applications of AI in agricultural imaging, specifically for fruit freshness classification. High-resolution and high-speed CMOS sensors, such as ON Semiconductor’s 13 MP AR1335 and XGS-12, play a critical role in capturing the quality images necessary for effective CNN-based classification and detection tasks. These sensors, integrated with edge computing platforms, provide the requisite image quality and processing capability needed for real-time AI applications.

The study emphasizes the use of MobileNetV2 for its balance between accuracy, latency, and the number of parameters, making it suitable for edge computing. MobileNetV2, featuring depthwise-separable convolutions and inverted residual structures, was chosen for its efficiency. The model was initialized with pretrained ImageNet weights and fine-tuned on a custom dataset of 30,000 images to classify fresh and rotten apples, bananas, and oranges, achieving a remarkable 97% accuracy. This approach leverages the strengths of transfer learning, enhancing the model's performance with a relatively small number of training parameters.

For object detection, the paper employs SSD (Single Shot Detector) with MobileNetV2 as the backbone, facilitating real-time detection and classification tasks. The choice of SSD over region proposal network-based architectures allows for faster and more efficient processing, crucial for edge platforms like NVIDIA Jetson Xavier. The detection model, however, shows a lower accuracy compared to the classification model, highlighting the need for further improvements and more extensive training data.

Training data included images from both a custom-captured dataset and a publicly available Kaggle fruits dataset, augmented with various transformations such as scaling, translation, rotation, and brightness variation to improve model robustness. The combination of high-quality images and diverse augmentations ensures the CNN models are well-trained and capable of handling real-world variations in the data.

Performance evaluation underscores the importance of high-quality images and optimized CNN architectures for achieving accurate real-time performance on edge platforms. While the image classification model achieved high accuracy, the object detection model showed potential for improvement. Ongoing efforts to expand the dataset and improve annotations are aimed at enhancing the accuracy and robustness of the object detection model, ensuring better real-time performance in practical applications. (2020 Tejaswini Ananthanarayana1, Raymond Ptucha, Sean C. Kelly)

Download the Dataset¶

In [2]:
from google.colab import files
In [3]:
files.upload()
Upload widget is only available when the cell has been executed in the current browser session. Please rerun this cell to enable.
Saving kaggle.json to kaggle.json
Out[3]:
{'kaggle.json': b'{"username":"vihanganadeesha","key":"edd9018f3186c182f48ca8e040319cde"}'}
In [4]:
# Make directory for Kaggle and move the kaggle.json file
!mkdir -p ~/.kaggle
!cp kaggle.json ~/.kaggle/
!chmod 600 ~/.kaggle/kaggle.json
In [5]:
!kaggle datasets download -d sriramr/fruits-fresh-and-rotten-for-classification
Dataset URL: https://www.kaggle.com/datasets/sriramr/fruits-fresh-and-rotten-for-classification
License(s): unknown
Downloading fruits-fresh-and-rotten-for-classification.zip to /content
100% 3.58G/3.58G [03:23<00:00, 17.3MB/s]
100% 3.58G/3.58G [03:23<00:00, 18.9MB/s]
In [6]:
!ls
fruits-fresh-and-rotten-for-classification.zip	kaggle.json  sample_data
In [7]:
!unzip -qq fruits-fresh-and-rotten-for-classification.zip
In [8]:
!ls
dataset  fruits-fresh-and-rotten-for-classification.zip  kaggle.json  sample_data

Preprocess the Dataset¶

In [9]:
!ls dataset
dataset  test  train

A validation dataset was created and 30 percent of the train data was moved to it in order to ensure that the model generalizes well to new, unseen data and does not simply memorize the training data. The main directory 'dataset' contains 3 sub directories as 'train, validation and train' where each includes a set of images of Apples, Bananas and Oranges classified as Fresh or Rotten.

In [10]:
import os
import shutil
import random

# Define paths
main_dir = 'dataset'
train_dir = os.path.join(main_dir, 'train')
validation_dir = os.path.join(main_dir, 'validation')
test_dir = os.path.join(main_dir, 'test')

# Create the validation directory structure
if not os.path.exists(validation_dir):
    os.mkdir(validation_dir)
    for category in os.listdir(train_dir):
        os.mkdir(os.path.join(validation_dir, category))

# Move 30% of the train data to validation data
for category in os.listdir(train_dir):
    category_path = os.path.join(train_dir, category)
    files = os.listdir(category_path)
    random.shuffle(files)
    validation_count = int(0.3 * len(files))

    for file_name in files[:validation_count]:
        src_path = os.path.join(category_path, file_name)
        dest_path = os.path.join(validation_dir, category, file_name)
        shutil.move(src_path, dest_path)

# Print to verify structure
print("Train directory structure:")
print(os.listdir(train_dir))
print("\nValidation directory structure:")
print(os.listdir(validation_dir))
print("\nTest directory structure:")
print(os.listdir(test_dir))
Train directory structure:
['freshbanana', 'rottenbanana', 'rottenapples', 'freshapples', 'freshoranges', 'rottenoranges']

Validation directory structure:
['freshbanana', 'rottenbanana', 'rottenapples', 'freshapples', 'freshoranges', 'rottenoranges']

Test directory structure:
['freshbanana', 'rottenbanana', 'rottenapples', 'freshapples', 'freshoranges', 'rottenoranges']

Pixel values from ranges [0,255] is converted to [0,1] by rescaling. This normalization helps the model train more efficiently by standardizing the input data. Generators were used to efficiently load and preprocess images in batches which is essential for handling large datasets by reducing memory usage and facilitating smoother training.The generators are configured to read images from the respective directories (train, validation, test), resize them to 224x224 pixels, and generate batches of images with their corresponding labels. Batch size of 32 is chosen because it provides a good balance between computational efficiency, memory usage and taining stability. It is also a well regarded default starting point.

In [11]:
import os
import tensorflow as tf
from tensorflow.keras.preprocessing.image import ImageDataGenerator

# Define paths
base_dir = 'dataset'
train_dir = os.path.join(base_dir, 'train')
validation_dir = os.path.join(base_dir, 'validation')
test_dir = os.path.join(base_dir, 'test')

# Verify the directories exist
print("Training directory contents:", os.listdir(train_dir))
print("Validation directory contents:", os.listdir(validation_dir))
print("Test directory contents:", os.listdir(test_dir))

# Only rescaling for all data
datagen = ImageDataGenerator(rescale=1./255)

# Generators
train_generator = datagen.flow_from_directory(
    train_dir,
    target_size=(224, 224),
    batch_size=32,
    class_mode='categorical'
)

validation_generator = datagen.flow_from_directory(
    validation_dir,
    target_size=(224, 224),
    batch_size=32,
    class_mode='categorical'
)

test_generator = datagen.flow_from_directory(
    test_dir,
    target_size=(224, 224),
    batch_size=32,
    class_mode='categorical'
)

# Print some information about the generators
print(f"Found {train_generator.samples} images belonging to {train_generator.num_classes} classes in training set.")
print(f"Found {validation_generator.samples} images belonging to {validation_generator.num_classes} classes in validation set.")
print(f"Found {test_generator.samples} images belonging to {test_generator.num_classes} classes in test set.")
Training directory contents: ['freshbanana', 'rottenbanana', 'rottenapples', 'freshapples', 'freshoranges', 'rottenoranges']
Validation directory contents: ['freshbanana', 'rottenbanana', 'rottenapples', 'freshapples', 'freshoranges', 'rottenoranges']
Test directory contents: ['freshbanana', 'rottenbanana', 'rottenapples', 'freshapples', 'freshoranges', 'rottenoranges']
Found 7634 images belonging to 6 classes.
Found 3267 images belonging to 6 classes.
Found 2698 images belonging to 6 classes.
Found 7634 images belonging to 6 classes in training set.
Found 3267 images belonging to 6 classes in validation set.
Found 2698 images belonging to 6 classes in test set.
In [12]:
import matplotlib.pyplot as plt
import numpy as np

# Function to display images
def display_images(generator, num_images):
    x_batch, y_batch = next(generator)  # Get a batch of images and labels
    plt.figure(figsize=(20, 10))
    for i in range(num_images):
        plt.subplot(4, 8, i + 1)
        plt.imshow(x_batch[i])
        plt.axis('off')
        # Convert one-hot encoded labels to class indices
        label_index = np.argmax(y_batch[i])
        plt.title('Fresh' if label_index == 1 else 'Rotten')
    plt.show()

# Display images from the train generator
print("Displaying images from the train generator:")
display_images(train_generator, 32)

# Display images from the validation generator
print("Displaying images from the validation generator:")
display_images(validation_generator, 32)

# Display images from the test generator
print("Displaying images from the test generator:")
display_images(test_generator, 32)
Displaying images from the train generator:
Displaying images from the validation generator:
Displaying images from the test generator:

Build and Train the MobileNetV2 Model¶

MobileNetV2 is chosen for fruit quality classification due to its efficiency, pre-training on ImageNet for general feature learning, transfer learning benefits for adapting to new datasets, scalability to handle different image sizes, and its state-of-the-art performance in image classification tasks. These factors collectively make MobileNetV2 a good choice for achieving accurate and efficient fruit quality classification models.

We will be checking the performance of the MobileNetV2 model for fruit classification using two different optimizers: Adam and RMSprop. The goal is to evaluate which optimizer achieves the best accuracy and convergence speed during training. This comparison allows us to determine the most effective optimizer for optimizing the model's training process, ensuring optimal performance in classifying fruits based on images.

Optimzer as Adam¶

Input Shape: MobileNetV2 requires inputs of shape (224, 224, 3), which corresponds to RGB images of size 224x224 pixels. include_top=False: We exclude the top layers (fully connected layers) of MobileNetV2, as we will add our own custom classifier on top.

weights='imagenet': We initialize the model with pre-trained weights on ImageNet, which helps in leveraging learned features for transfer learning.

Freezing Layers: We freeze the first 100 layers of MobileNetV2. Freezing prevents their weights from being updated during training, which is beneficial when using transfer learning to retain the learned representations and avoid overfitting on a small dataset.

Unfreezing Layers: We unfreeze (allow training) for layers beyond the first 100. This gives the model flexibility to adapt and fine-tune these layers to our specific dataset.

Global Average Pooling: Reduces the spatial dimensions of the MobileNetV2 output to a vector for each channel. It reduces each feature map to a single value per map, thus converting the 3D tensor output by MobileNetV2 to a 1D tensor.

Dense Layer (128 neurons): Introduces a fully connected layer with ReLU activation, which adds non-linearity to the model and avoids vanishing gradientproblems while helping to learn complex patterns.

Dropout (0.5): Regularizes the model by randomly setting half of the input units to 0 at each update during training, which helps prevent overfitting.

Dense Output Layer (num_classes neurons): Produces probabilities for each class using softmax activation, suitable for multi-class classification.

Learning Rate: Initialized at 0.0001, a common starting point for fine-tuning pre-trained models.

Loss Function: Categorical crossentropy is appropriate for multi-class classification tasks.

Metrics: Accuracy is used to monitor the model's performance during training.

In [13]:
from tensorflow.keras.applications import MobileNetV2
from tensorflow.keras import layers, models
from tensorflow.keras.optimizers import Adam

# Load MobileNetV2 with pre-trained weights
base_model = MobileNetV2(input_shape=(224, 224, 3),
                         include_top=False,
                         weights='imagenet')

# Freeze the initial layers of the base model
for layer in base_model.layers[:100]:  # Freeze the first 100 layers
    layer.trainable = False

# Unfreeze the rest of the layers
for layer in base_model.layers[100:]:
    layer.trainable = True

# Get the number of classes from the training generator
num_classes = train_generator.num_classes

# Add custom layers on top of the base model
model = models.Sequential([
    base_model,
    layers.GlobalAveragePooling2D(),
    layers.Dense(128, activation='relu'),
    layers.Dropout(0.5),
    layers.Dense(num_classes, activation='softmax')
])

# Compile the model
model.compile(optimizer=Adam(learning_rate=0.0001),
              loss='categorical_crossentropy',
              metrics=['accuracy'])

# Print the model summary
model.summary()

# Train the model
history = model.fit(
    train_generator,
    epochs=50,
    validation_data=validation_generator
)
Downloading data from https://storage.googleapis.com/tensorflow/keras-applications/mobilenet_v2/mobilenet_v2_weights_tf_dim_ordering_tf_kernels_1.0_224_no_top.h5
9406464/9406464 [==============================] - 1s 0us/step
Model: "sequential"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 mobilenetv2_1.00_224 (Func  (None, 7, 7, 1280)        2257984   
 tional)                                                         
                                                                 
 global_average_pooling2d (  (None, 1280)              0         
 GlobalAveragePooling2D)                                         
                                                                 
 dense (Dense)               (None, 128)               163968    
                                                                 
 dropout (Dropout)           (None, 128)               0         
                                                                 
 dense_1 (Dense)             (None, 6)                 774       
                                                                 
=================================================================
Total params: 2422726 (9.24 MB)
Trainable params: 2026182 (7.73 MB)
Non-trainable params: 396544 (1.51 MB)
_________________________________________________________________
Epoch 1/50
239/239 [==============================] - 77s 255ms/step - loss: 0.3156 - accuracy: 0.8928 - val_loss: 1.1020 - val_accuracy: 0.6615
Epoch 2/50
239/239 [==============================] - 54s 226ms/step - loss: 0.0525 - accuracy: 0.9826 - val_loss: 0.3686 - val_accuracy: 0.8818
Epoch 3/50
239/239 [==============================] - 54s 225ms/step - loss: 0.0243 - accuracy: 0.9928 - val_loss: 0.2238 - val_accuracy: 0.9281
Epoch 4/50
239/239 [==============================] - 53s 221ms/step - loss: 0.0132 - accuracy: 0.9965 - val_loss: 0.1435 - val_accuracy: 0.9605
Epoch 5/50
239/239 [==============================] - 53s 221ms/step - loss: 0.0118 - accuracy: 0.9961 - val_loss: 0.0363 - val_accuracy: 0.9878
Epoch 6/50
239/239 [==============================] - 54s 224ms/step - loss: 0.0126 - accuracy: 0.9959 - val_loss: 0.0873 - val_accuracy: 0.9709
Epoch 7/50
239/239 [==============================] - 54s 224ms/step - loss: 0.0057 - accuracy: 0.9986 - val_loss: 0.0630 - val_accuracy: 0.9798
Epoch 8/50
239/239 [==============================] - 53s 223ms/step - loss: 0.0102 - accuracy: 0.9979 - val_loss: 0.0692 - val_accuracy: 0.9807
Epoch 9/50
239/239 [==============================] - 53s 220ms/step - loss: 0.0104 - accuracy: 0.9963 - val_loss: 0.3251 - val_accuracy: 0.9360
Epoch 10/50
239/239 [==============================] - 54s 224ms/step - loss: 0.0092 - accuracy: 0.9975 - val_loss: 0.0467 - val_accuracy: 0.9835
Epoch 11/50
239/239 [==============================] - 54s 227ms/step - loss: 0.0084 - accuracy: 0.9975 - val_loss: 0.0203 - val_accuracy: 0.9954
Epoch 12/50
239/239 [==============================] - 53s 223ms/step - loss: 0.0090 - accuracy: 0.9972 - val_loss: 0.0182 - val_accuracy: 0.9963
Epoch 13/50
239/239 [==============================] - 53s 223ms/step - loss: 0.0046 - accuracy: 0.9980 - val_loss: 0.0180 - val_accuracy: 0.9957
Epoch 14/50
239/239 [==============================] - 54s 224ms/step - loss: 0.0100 - accuracy: 0.9971 - val_loss: 0.0751 - val_accuracy: 0.9832
Epoch 15/50
239/239 [==============================] - 54s 227ms/step - loss: 0.0127 - accuracy: 0.9966 - val_loss: 0.0282 - val_accuracy: 0.9923
Epoch 16/50
239/239 [==============================] - 60s 251ms/step - loss: 0.0091 - accuracy: 0.9972 - val_loss: 0.0507 - val_accuracy: 0.9875
Epoch 17/50
239/239 [==============================] - 53s 223ms/step - loss: 0.0064 - accuracy: 0.9978 - val_loss: 0.1216 - val_accuracy: 0.9801
Epoch 18/50
239/239 [==============================] - 57s 237ms/step - loss: 0.0026 - accuracy: 0.9992 - val_loss: 0.0169 - val_accuracy: 0.9960
Epoch 19/50
239/239 [==============================] - 54s 228ms/step - loss: 0.0066 - accuracy: 0.9986 - val_loss: 0.0137 - val_accuracy: 0.9966
Epoch 20/50
239/239 [==============================] - 53s 222ms/step - loss: 0.0078 - accuracy: 0.9974 - val_loss: 0.0240 - val_accuracy: 0.9939
Epoch 21/50
239/239 [==============================] - 53s 222ms/step - loss: 0.0063 - accuracy: 0.9980 - val_loss: 0.0204 - val_accuracy: 0.9954
Epoch 22/50
239/239 [==============================] - 54s 225ms/step - loss: 0.0095 - accuracy: 0.9972 - val_loss: 0.0160 - val_accuracy: 0.9957
Epoch 23/50
239/239 [==============================] - 53s 221ms/step - loss: 0.0079 - accuracy: 0.9974 - val_loss: 0.0080 - val_accuracy: 0.9972
Epoch 24/50
239/239 [==============================] - 53s 220ms/step - loss: 0.0071 - accuracy: 0.9979 - val_loss: 0.0036 - val_accuracy: 0.9991
Epoch 25/50
239/239 [==============================] - 53s 220ms/step - loss: 0.0069 - accuracy: 0.9987 - val_loss: 0.0092 - val_accuracy: 0.9969
Epoch 26/50
239/239 [==============================] - 53s 222ms/step - loss: 0.0064 - accuracy: 0.9987 - val_loss: 0.0133 - val_accuracy: 0.9969
Epoch 27/50
239/239 [==============================] - 54s 227ms/step - loss: 0.0027 - accuracy: 0.9990 - val_loss: 0.0048 - val_accuracy: 0.9988
Epoch 28/50
239/239 [==============================] - 53s 223ms/step - loss: 0.0085 - accuracy: 0.9975 - val_loss: 0.2281 - val_accuracy: 0.9608
Epoch 29/50
239/239 [==============================] - 53s 224ms/step - loss: 0.0047 - accuracy: 0.9986 - val_loss: 0.0106 - val_accuracy: 0.9963
Epoch 30/50
239/239 [==============================] - 53s 223ms/step - loss: 0.0077 - accuracy: 0.9979 - val_loss: 0.0071 - val_accuracy: 0.9982
Epoch 31/50
239/239 [==============================] - 56s 234ms/step - loss: 0.0039 - accuracy: 0.9995 - val_loss: 0.0220 - val_accuracy: 0.9960
Epoch 32/50
239/239 [==============================] - 53s 222ms/step - loss: 0.0024 - accuracy: 0.9995 - val_loss: 0.0102 - val_accuracy: 0.9972
Epoch 33/50
239/239 [==============================] - 53s 223ms/step - loss: 0.0016 - accuracy: 0.9992 - val_loss: 0.0029 - val_accuracy: 0.9991
Epoch 34/50
239/239 [==============================] - 52s 220ms/step - loss: 0.0011 - accuracy: 0.9996 - val_loss: 0.0026 - val_accuracy: 0.9994
Epoch 35/50
239/239 [==============================] - 53s 220ms/step - loss: 0.0039 - accuracy: 0.9991 - val_loss: 0.0283 - val_accuracy: 0.9923
Epoch 36/50
239/239 [==============================] - 60s 249ms/step - loss: 0.0125 - accuracy: 0.9966 - val_loss: 0.0020 - val_accuracy: 0.9994
Epoch 37/50
239/239 [==============================] - 52s 216ms/step - loss: 0.0052 - accuracy: 0.9987 - val_loss: 0.0060 - val_accuracy: 0.9991
Epoch 38/50
239/239 [==============================] - 53s 221ms/step - loss: 0.0158 - accuracy: 0.9969 - val_loss: 0.0295 - val_accuracy: 0.9939
Epoch 39/50
239/239 [==============================] - 58s 243ms/step - loss: 0.0082 - accuracy: 0.9978 - val_loss: 0.0117 - val_accuracy: 0.9982
Epoch 40/50
239/239 [==============================] - 54s 227ms/step - loss: 0.0023 - accuracy: 0.9995 - val_loss: 0.0079 - val_accuracy: 0.9982
Epoch 41/50
239/239 [==============================] - 53s 224ms/step - loss: 0.0053 - accuracy: 0.9983 - val_loss: 0.0050 - val_accuracy: 0.9991
Epoch 42/50
239/239 [==============================] - 53s 224ms/step - loss: 0.0016 - accuracy: 0.9997 - val_loss: 0.0050 - val_accuracy: 0.9988
Epoch 43/50
239/239 [==============================] - 53s 223ms/step - loss: 0.0037 - accuracy: 0.9982 - val_loss: 0.0057 - val_accuracy: 0.9988
Epoch 44/50
239/239 [==============================] - 53s 224ms/step - loss: 0.0022 - accuracy: 0.9995 - val_loss: 0.0029 - val_accuracy: 0.9991
Epoch 45/50
239/239 [==============================] - 60s 251ms/step - loss: 0.0069 - accuracy: 0.9986 - val_loss: 0.0418 - val_accuracy: 0.9930
Epoch 46/50
239/239 [==============================] - 53s 224ms/step - loss: 0.0023 - accuracy: 0.9992 - val_loss: 0.0167 - val_accuracy: 0.9969
Epoch 47/50
239/239 [==============================] - 56s 232ms/step - loss: 0.0014 - accuracy: 0.9996 - val_loss: 0.0063 - val_accuracy: 0.9979
Epoch 48/50
239/239 [==============================] - 53s 221ms/step - loss: 0.0020 - accuracy: 0.9993 - val_loss: 0.0024 - val_accuracy: 0.9994
Epoch 49/50
239/239 [==============================] - 53s 221ms/step - loss: 0.0030 - accuracy: 0.9996 - val_loss: 0.0033 - val_accuracy: 0.9991
Epoch 50/50
239/239 [==============================] - 54s 227ms/step - loss: 5.0963e-04 - accuracy: 0.9997 - val_loss: 0.0159 - val_accuracy: 0.9954
In [14]:
import matplotlib.pyplot as plt

# Extract accuracy values
train_accuracy = history.history['accuracy']
val_accuracy = history.history['val_accuracy']

# Plot accuracy
epochs = range(1, len(train_accuracy) + 1)
plt.plot(epochs, train_accuracy, 'b', label='Training accuracy')
plt.plot(epochs, val_accuracy, 'r', label='Validation accuracy')
plt.title('Training and Validation Accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()
plt.show()

The graph illustrates the training and validation accuracy of the model over 50 epochs, showing a rapid initial increase in accuracy, followed by stabilization at high values near 1.0 (or 100%). Both the training and validation accuracies closely align, indicating minimal overfitting and suggesting that the model generalizes well to unseen data. The model has effectively learned the distinguishing features between fresh and rotten fruits, achieving excellent performance on both training and validation sets. The stabilization of accuracy after the initial epochs suggests that 50 epochs are sufficient for training, with further training unlikely to yield significant improvements. Overall, the model demonstrates robust and effective learning, promising good performance on new, unseen data.

In [15]:
import matplotlib.pyplot as plt

# Extract loss values
train_loss = history.history['loss']
val_loss = history.history['val_loss']

# Plot loss
epochs = range(1, len(train_loss) + 1)
plt.plot(epochs, train_loss, 'b', label='Training loss')
plt.plot(epochs, val_loss, 'r', label='Validation loss')
plt.title('Training and Validation Loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()

The graph illustrates the training and validation loss of the model for 50 epochs, showing a rapid initial decrease in loss for both the training and validation sets. Both losses stabilize at low values close to zero, indicating the model is minimizing errors effectively. The training loss consistently remains slightly lower than the validation loss, but the difference is minimal, suggesting good generalization without significant overfitting. The occasional spikes in validation loss are common and indicate transient fluctuations, but they do not affect the overall trend. The convergence to low loss values after the initial epochs suggests that the model has effectively learned the features required for classifying fresh and rotten fruits, and further training is unlikely to yield significant improvements. Overall, the graph demonstrates successful model training, with effective loss minimization and good generalization to unseen data.

Evaluate the model¶

In [16]:
# Evaluate the model on the test data
test_loss, test_acc = model.evaluate(test_generator)
print(f"Test accuracy: {test_acc * 100:.2f}%")
85/85 [==============================] - 17s 200ms/step - loss: 0.0082 - accuracy: 0.9981
Test accuracy: 99.81%

The test accuracy of 99.59% indicates that the model is highly effective at classifying fresh and rotten fruits, showing excellent performance on unseen data. This high accuracy, along with consistent training and validation results, suggests that the model has effectively learned the necessary features and generalizes well without overfitting. The robustness and reliability of the model are evident, making it well-suited for practical applications in fruit classification.

Optimizer as RMSprop (Root Mean Square Propagation)¶

In [17]:
from tensorflow.keras.applications import MobileNetV2
from tensorflow.keras import layers, models
from tensorflow.keras.optimizers import RMSprop

# Load MobileNetV2 with pre-trained weights
base_model = MobileNetV2(input_shape=(224, 224, 3),
                         include_top=False,
                         weights='imagenet')

# Freeze the initial layers of the base model
for layer in base_model.layers[:100]:  # Freeze the first 100 layers
    layer.trainable = False

# Unfreeze the rest of the layers
for layer in base_model.layers[100:]:
    layer.trainable = True

# Get the number of classes from the training generator
num_classes = train_generator.num_classes

# Add custom layers on top of the base model
model = models.Sequential([
    base_model,
    layers.GlobalAveragePooling2D(),
    layers.Dense(128, activation='relu'),
    layers.Dropout(0.5),
    layers.Dense(num_classes, activation='softmax')  # Assuming binary classification
])

# Compile the model with RMSprop optimizer
model.compile(optimizer=RMSprop(learning_rate=0.0001),
              loss='categorical_crossentropy',
              metrics=['accuracy'])

# Print the model summary
model.summary()

# Train the model
history = model.fit(
    train_generator,
    epochs=50,
    validation_data=validation_generator
)
Model: "sequential_1"
_________________________________________________________________
 Layer (type)                Output Shape              Param #   
=================================================================
 mobilenetv2_1.00_224 (Func  (None, 7, 7, 1280)        2257984   
 tional)                                                         
                                                                 
 global_average_pooling2d_1  (None, 1280)              0         
  (GlobalAveragePooling2D)                                       
                                                                 
 dense_2 (Dense)             (None, 128)               163968    
                                                                 
 dropout_1 (Dropout)         (None, 128)               0         
                                                                 
 dense_3 (Dense)             (None, 6)                 774       
                                                                 
=================================================================
Total params: 2422726 (9.24 MB)
Trainable params: 2026182 (7.73 MB)
Non-trainable params: 396544 (1.51 MB)
_________________________________________________________________
Epoch 1/50
239/239 [==============================] - 67s 252ms/step - loss: 0.2415 - accuracy: 0.9206 - val_loss: 0.9871 - val_accuracy: 0.7221
Epoch 2/50
239/239 [==============================] - 53s 223ms/step - loss: 0.0309 - accuracy: 0.9910 - val_loss: 0.6725 - val_accuracy: 0.8405
Epoch 3/50
239/239 [==============================] - 57s 240ms/step - loss: 0.0125 - accuracy: 0.9952 - val_loss: 0.2541 - val_accuracy: 0.9366
Epoch 4/50
239/239 [==============================] - 55s 231ms/step - loss: 0.0108 - accuracy: 0.9966 - val_loss: 0.1021 - val_accuracy: 0.9688
Epoch 5/50
239/239 [==============================] - 54s 226ms/step - loss: 0.0137 - accuracy: 0.9961 - val_loss: 0.1283 - val_accuracy: 0.9617
Epoch 6/50
239/239 [==============================] - 54s 227ms/step - loss: 0.0073 - accuracy: 0.9978 - val_loss: 0.0854 - val_accuracy: 0.9740
Epoch 7/50
239/239 [==============================] - 53s 223ms/step - loss: 0.0071 - accuracy: 0.9984 - val_loss: 0.0592 - val_accuracy: 0.9829
Epoch 8/50
239/239 [==============================] - 54s 225ms/step - loss: 0.0088 - accuracy: 0.9972 - val_loss: 0.0333 - val_accuracy: 0.9896
Epoch 9/50
239/239 [==============================] - 53s 223ms/step - loss: 0.0038 - accuracy: 0.9983 - val_loss: 0.0056 - val_accuracy: 0.9985
Epoch 10/50
239/239 [==============================] - 53s 220ms/step - loss: 0.0076 - accuracy: 0.9978 - val_loss: 0.0112 - val_accuracy: 0.9979
Epoch 11/50
239/239 [==============================] - 54s 225ms/step - loss: 0.0029 - accuracy: 0.9990 - val_loss: 0.0104 - val_accuracy: 0.9976
Epoch 12/50
239/239 [==============================] - 53s 222ms/step - loss: 0.0040 - accuracy: 0.9982 - val_loss: 0.0052 - val_accuracy: 0.9976
Epoch 13/50
239/239 [==============================] - 53s 222ms/step - loss: 0.0031 - accuracy: 0.9987 - val_loss: 0.0145 - val_accuracy: 0.9972
Epoch 14/50
239/239 [==============================] - 54s 224ms/step - loss: 0.0021 - accuracy: 0.9990 - val_loss: 0.0027 - val_accuracy: 0.9991
Epoch 15/50
239/239 [==============================] - 53s 220ms/step - loss: 0.0028 - accuracy: 0.9993 - val_loss: 0.0140 - val_accuracy: 0.9969
Epoch 16/50
239/239 [==============================] - 53s 221ms/step - loss: 0.0056 - accuracy: 0.9988 - val_loss: 0.0244 - val_accuracy: 0.9966
Epoch 17/50
239/239 [==============================] - 53s 222ms/step - loss: 0.0035 - accuracy: 0.9991 - val_loss: 0.0092 - val_accuracy: 0.9982
Epoch 18/50
239/239 [==============================] - 53s 220ms/step - loss: 0.0023 - accuracy: 0.9993 - val_loss: 0.0034 - val_accuracy: 0.9991
Epoch 19/50
239/239 [==============================] - 54s 227ms/step - loss: 0.0021 - accuracy: 0.9993 - val_loss: 0.0102 - val_accuracy: 0.9979
Epoch 20/50
239/239 [==============================] - 54s 226ms/step - loss: 0.0026 - accuracy: 0.9990 - val_loss: 0.0061 - val_accuracy: 0.9988
Epoch 21/50
239/239 [==============================] - 52s 218ms/step - loss: 0.0012 - accuracy: 0.9999 - val_loss: 0.0053 - val_accuracy: 0.9982
Epoch 22/50
239/239 [==============================] - 60s 250ms/step - loss: 0.0021 - accuracy: 0.9992 - val_loss: 0.0046 - val_accuracy: 0.9994
Epoch 23/50
239/239 [==============================] - 52s 219ms/step - loss: 0.0012 - accuracy: 0.9995 - val_loss: 0.0067 - val_accuracy: 0.9988
Epoch 24/50
239/239 [==============================] - 53s 220ms/step - loss: 0.0020 - accuracy: 0.9996 - val_loss: 0.0048 - val_accuracy: 0.9985
Epoch 25/50
239/239 [==============================] - 54s 224ms/step - loss: 0.0019 - accuracy: 0.9993 - val_loss: 0.0065 - val_accuracy: 0.9991
Epoch 26/50
239/239 [==============================] - 53s 220ms/step - loss: 9.3328e-04 - accuracy: 0.9996 - val_loss: 0.0050 - val_accuracy: 0.9991
Epoch 27/50
239/239 [==============================] - 53s 223ms/step - loss: 0.0028 - accuracy: 0.9992 - val_loss: 0.0040 - val_accuracy: 0.9988
Epoch 28/50
239/239 [==============================] - 53s 222ms/step - loss: 0.0028 - accuracy: 0.9992 - val_loss: 0.0036 - val_accuracy: 0.9991
Epoch 29/50
239/239 [==============================] - 53s 224ms/step - loss: 0.0015 - accuracy: 0.9995 - val_loss: 0.0072 - val_accuracy: 0.9972
Epoch 30/50
239/239 [==============================] - 58s 244ms/step - loss: 0.0013 - accuracy: 0.9997 - val_loss: 0.0018 - val_accuracy: 0.9994
Epoch 31/50
239/239 [==============================] - 52s 218ms/step - loss: 3.4316e-04 - accuracy: 0.9999 - val_loss: 1.2010e-04 - val_accuracy: 1.0000
Epoch 32/50
239/239 [==============================] - 53s 222ms/step - loss: 0.0022 - accuracy: 0.9995 - val_loss: 4.9893e-04 - val_accuracy: 0.9997
Epoch 33/50
239/239 [==============================] - 54s 225ms/step - loss: 0.0010 - accuracy: 0.9996 - val_loss: 0.0031 - val_accuracy: 0.9991
Epoch 34/50
239/239 [==============================] - 54s 226ms/step - loss: 0.0025 - accuracy: 0.9991 - val_loss: 0.0146 - val_accuracy: 0.9976
Epoch 35/50
239/239 [==============================] - 52s 219ms/step - loss: 1.3560e-04 - accuracy: 1.0000 - val_loss: 0.0024 - val_accuracy: 0.9994
Epoch 36/50
239/239 [==============================] - 52s 219ms/step - loss: 0.0012 - accuracy: 0.9997 - val_loss: 0.0015 - val_accuracy: 0.9991
Epoch 37/50
239/239 [==============================] - 53s 224ms/step - loss: 0.0016 - accuracy: 0.9995 - val_loss: 0.0093 - val_accuracy: 0.9988
Epoch 38/50
239/239 [==============================] - 53s 222ms/step - loss: 0.0033 - accuracy: 0.9993 - val_loss: 0.0016 - val_accuracy: 0.9997
Epoch 39/50
239/239 [==============================] - 57s 240ms/step - loss: 3.2095e-04 - accuracy: 0.9999 - val_loss: 7.2674e-05 - val_accuracy: 1.0000
Epoch 40/50
239/239 [==============================] - 53s 220ms/step - loss: 2.3548e-04 - accuracy: 1.0000 - val_loss: 0.0012 - val_accuracy: 0.9994
Epoch 41/50
239/239 [==============================] - 53s 223ms/step - loss: 0.0010 - accuracy: 0.9996 - val_loss: 0.0014 - val_accuracy: 0.9997
Epoch 42/50
239/239 [==============================] - 53s 220ms/step - loss: 4.1421e-04 - accuracy: 0.9997 - val_loss: 0.0035 - val_accuracy: 0.9994
Epoch 43/50
239/239 [==============================] - 54s 225ms/step - loss: 5.6273e-04 - accuracy: 0.9997 - val_loss: 0.0063 - val_accuracy: 0.9985
Epoch 44/50
239/239 [==============================] - 53s 223ms/step - loss: 1.9883e-04 - accuracy: 1.0000 - val_loss: 0.0015 - val_accuracy: 0.9991
Epoch 45/50
239/239 [==============================] - 52s 218ms/step - loss: 0.0010 - accuracy: 0.9997 - val_loss: 0.0010 - val_accuracy: 0.9994
Epoch 46/50
239/239 [==============================] - 53s 224ms/step - loss: 6.3683e-04 - accuracy: 0.9997 - val_loss: 5.8313e-04 - val_accuracy: 0.9997
Epoch 47/50
239/239 [==============================] - 52s 217ms/step - loss: 7.1555e-05 - accuracy: 1.0000 - val_loss: 0.0012 - val_accuracy: 0.9994
Epoch 48/50
239/239 [==============================] - 54s 226ms/step - loss: 1.0947e-04 - accuracy: 1.0000 - val_loss: 2.7698e-04 - val_accuracy: 1.0000
Epoch 49/50
239/239 [==============================] - 52s 219ms/step - loss: 0.0012 - accuracy: 0.9997 - val_loss: 5.3641e-04 - val_accuracy: 0.9997
Epoch 50/50
239/239 [==============================] - 51s 215ms/step - loss: 7.2074e-05 - accuracy: 1.0000 - val_loss: 5.4222e-04 - val_accuracy: 0.9997
In [18]:
import matplotlib.pyplot as plt

# Extract accuracy values
train_accuracy = history.history['accuracy']
val_accuracy = history.history['val_accuracy']

# Plot accuracy
epochs = range(1, len(train_accuracy) + 1)
plt.plot(epochs, train_accuracy, 'b', label='Training accuracy')
plt.plot(epochs, val_accuracy, 'r', label='Validation accuracy')
plt.title('Training and Validation Accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()
plt.show()

The graph shows the training and validation accuracy of the model with RMSprop for 50 epochs, with accuracy rapidly increasing and stabilizing near 100%, indicating excellent performance and minimal overfitting. The model effectively distinguishes between fresh and rotten fruits, and further training beyond 15 epochs yields little improvement. RMSprop's smoother performance compared to Adam is due to its conservative learning rate adjustments without momentum, resulting in more stable and consistent updates, while Adam's adaptive learning rate and momentum can cause occasional spikes in accuracy.

In [19]:
import matplotlib.pyplot as plt

# Extract loss values
train_loss = history.history['loss']
val_loss = history.history['val_loss']

# Plot loss
epochs = range(1, len(train_loss) + 1)
plt.plot(epochs, train_loss, 'b', label='Training loss')
plt.plot(epochs, val_loss, 'r', label='Validation loss')
plt.title('Training and Validation Loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()
plt.show()

The occasional spikes in validation loss when using the Adam optimizer compared to RMSprop can be attributed to Adam's adaptive learning rate and momentum, which can cause transient fluctuations. Both optimizers show a rapid initial decrease in loss for both training and validation sets, eventually stabilizing at low values close to zero, indicating effective error minimization. RMSprop's more conservative approach results in smoother and more stable convergence, minimizing such fluctuations and ensuring a steadier decrease in loss over time.

Evaluate the model¶

In [20]:
# Evaluate the model on the test data
test_loss, test_acc = model.evaluate(test_generator)
print(f"Test accuracy: {test_acc * 100:.2f}%")
85/85 [==============================] - 13s 150ms/step - loss: 0.0024 - accuracy: 0.9989
Test accuracy: 99.89%

The test accuracy of 99.89% with RMSprop indicates that the model is highly effective at classifying fresh and rotten fruits, even outperforming the Adam optimizer's test accuracy of 99.59%. This high accuracy, along with consistent training and validation results, shows that the model has effectively learned the necessary features and generalizes well without overfitting. The robustness and reliability of the model are evident, making it exceptionally well-suited for practical applications in fruit classification. Comparing both, RMSprop demonstrates slightly better performance and stability, making it the superior optimizer for this task.

In [ ]: